package com.powernode.common.aspect;

import com.powernode.common.constant.RedisConstant;
import com.powernode.common.execption.PowerException;
import com.powernode.common.result.ResultCodeEnum;
import com.powernode.common.util.AuthContextHolder;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 登录校验切面类
 * 该类使用AOP技术，在目标方法执行前进行登录校验，确保只有已登录用户才能访问受保护的资源
 */
@Aspect
@Component
public class PowerLoginAspect {
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 环绕通知方法，用于处理用户登录认证
     * 主要功能：从Redis中根据token获取用户ID，并设置到线程上下文中
     *
     * @param joinPoint 连接点，代表被拦截的目标方法
     * @return 目标方法的执行结果
     * @throws PowerException 当token为空或无效时抛出登录认证异常
     * <p>
     * 本质：从redis中根据token获取用户的id
     * 1.获取请求对象
     * 2.获取请求头中的token
     * 3.根据token获取用户id
     * 4.将用户id设置到AuthContextHolder中
     * 切入点表达式：1.所有的controller方法中  2.带PowerLogin注解的方法
     */
    @Around("execution(* com.powernode.*.controller.*.*(..)) && @annotation(com.powernode.common.annotation.PowerLogin)")
    public Object process(ProceedingJoinPoint joinPoint) {
        // 获取当前请求的属性信息
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 从请求属性中获取HttpServletRequest对象
        HttpServletRequest request = requestAttributes.getRequest();

        // 从请求头中获取token
        String token = request.getHeader("token");
        // 检查token是否为空或空白字符串
        if (!StringUtils.hasText(token)) {
            // 如果token为空，抛出登录认证异常
            throw new PowerException(ResultCodeEnum.LOGIN_AUTH);
        }

        // 根据token从Redis中获取用户ID
        String userId = redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token).toString();
        // 检查用户ID是否为空或空白字符串
        if (!StringUtils.hasText(userId)) {
            // 如果用户ID为空，说明token无效或已过期，抛出登录认证异常
            throw new PowerException(ResultCodeEnum.LOGIN_AUTH);
        }

        // 将用户ID设置到认证上下文持有者中，供后续业务逻辑使用
        AuthContextHolder.setUserId(Long.valueOf(userId));
        try {
            // 继续执行目标方法
            return joinPoint.proceed();
        } catch (Throwable e) {
            // 如果目标方法执行过程中出现异常，包装成运行时异常抛出
            throw new RuntimeException(e);
        }
    }
}
